﻿// This software is part of the Autofac IoC container
// Copyright (c) 2007 - 2011 Autofac Contributors
// http://autofac.org
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Autofac;
using Microsoft.Scripting;
using System.Reflection;
using IronPython.Hosting;
using Microsoft.Scripting.Hosting;

namespace AutofacContrib.Scriptable
{
    public enum ScriptEngineType
    {
        Python
    }

    /// <summary>
    /// Provides extension methods for configuring the container by using 
    /// the DLR (Dynamic Language Runtime) scripting. Currently only 
    /// IronPython [http://ironpython.net/] is supported.
    /// An instance of <see cref="ContainerBuilder"/> is available in the 
    /// script named "containerBuilder" and can be used to perform the usual
    /// container configuration. Additionally, this type of container configuration
    /// enables one more scenario - the ability to define dynamic types in the script
    /// that can inherit statically defined types (or implement statical interfaces)
    /// and then register them as implementations of those services. This can be useful
    /// in the situations when the behaviour of some component needs to be changed 
    /// dinamically. For an example of a script see the unit-test project.
    /// </summary>
    public static class ScriptableBuilder
    {
        /// <summary>
        /// Executes the provided DLR script file.
        /// </summary>
        /// <param name="containerBuilder">The container builder</param>
        /// <param name="scriptFilePath">The path of the script file</param>
        /// <param name="scriptEngineType">Script engine type</param>
        public static void RegisterFromScriptFile(this ContainerBuilder containerBuilder, string scriptFilePath, ScriptEngineType scriptEngineType)
        {
            ScriptEngine scriptEngine = null;
            if (scriptEngineType == ScriptEngineType.Python)
            {
                scriptEngine = Python.CreateEngine();
            }
            else
            {
                throw new ArgumentException(String.Format("Unknown script engine '{0}'.", scriptEngineType), "scriptEngineType");
            }
            var scriptScope = scriptEngine.CreateScope();
            var scriptSource = scriptEngine.CreateScriptSourceFromFile(scriptFilePath);
            scriptScope.SetVariable("builder", containerBuilder);
            scriptSource.Compile();
            scriptSource.Execute(scriptScope);
        }

        /// <summary>
        /// Executes the provided DLR script string.
        /// </summary>
        /// <param name="containerBuilder">The container builder</param>
        /// <param name="scriptFilePath">The content of the script</param>
        /// <param name="scriptEngineType">Script engine type</param>
        public static void RegisterFromScript(this ContainerBuilder containerBuilder, string scriptText, ScriptEngineType scriptEngineType)
        {
            ScriptEngine scriptEngine = null;
            if (scriptEngineType == ScriptEngineType.Python)
            {
                scriptEngine = Python.CreateEngine();
            }
            else
            {
                throw new ArgumentException(String.Format("Unknown script engine '{0}'.", scriptEngineType), "scriptEngineType");
            }
            var scriptScope = scriptEngine.CreateScope();
            var scriptSource = scriptEngine.CreateScriptSourceFromString(scriptText);
            scriptScope.SetVariable("builder", containerBuilder);
            scriptSource.Compile();
            scriptSource.Execute(scriptScope);
        }
    }

    /// <summary>
    /// Exposes functionality provided by <see cref="ContainerBuilder"/> via
    /// a module so that it can be used within application configuration file.
    /// </summary>
    public class ScriptableModule : Autofac.Module
    {
        /// <summary>
        /// If set to false, the the module can be disabled without
        /// removing it from the application configuration file.
        /// </summary>
        public bool Enabled { get; set; }

        /// <summary>
        /// The fype of the script.
        /// </summary>
        public ScriptEngineType ScriptEngine { get; set; }

        /// <summary>
        /// The path of the script file.
        /// </summary>
        public string ScriptPath { get; set; }

        public ScriptableModule()
        {
            this.Enabled = true;
            this.ScriptEngine = ScriptEngineType.Python;
        }

        protected override void Load(ContainerBuilder builder)
        {
            if (String.IsNullOrEmpty(this.ScriptPath))
            {
                throw new InvalidOperationException("Cannot perform load - ScriptPath property is not set.");
            }
            if (this.Enabled)
            {
                builder.RegisterFromScriptFile(this.ScriptPath, this.ScriptEngine);
            }
        }
    }
}
